{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "_jQ1tEQCxwRx"
   },
   "source": [
    "##### Copyright 2019 The TensorFlow Authors."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "cellView": "form",
    "colab": {},
    "colab_type": "code",
    "id": "V_sgB_5dx1f1"
   },
   "outputs": [],
   "source": [
    "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
    "# you may not use this file except in compliance with the License.\n",
    "# You may obtain a copy of the License at\n",
    "#\n",
    "# https://www.apache.org/licenses/LICENSE-2.0\n",
    "#\n",
    "# Unless required by applicable law or agreed to in writing, software\n",
    "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
    "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
    "# See the License for the specific language governing permissions and\n",
    "# limitations under the License."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "rF2x3qooyBTI"
   },
   "source": [
    "# Deep Convolutional Generative Adversarial Network"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "0TD5ZrvEMbhZ"
   },
   "source": [
    "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
    "  <td>\n",
    "    <a target=\"_blank\" href=\"https://www.tensorflow.org/tutorials/generative/dcgan\">\n",
    "    <img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\" />\n",
    "    View on TensorFlow.org</a>\n",
    "  </td>\n",
    "  <td>\n",
    "    <a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs/blob/master/site/en/tutorials/generative/dcgan.ipynb\">\n",
    "    <img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" />\n",
    "    Run in Google Colab</a>\n",
    "  </td>\n",
    "  <td>\n",
    "    <a target=\"_blank\" href=\"https://github.com/tensorflow/docs/blob/master/site/en/tutorials/generative/dcgan.ipynb\">\n",
    "    <img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\" />\n",
    "    View source on GitHub</a>\n",
    "  </td>\n",
    "  <td>\n",
    "    <a href=\"https://storage.googleapis.com/tensorflow_docs/docs/site/en/tutorials/generative/dcgan.ipynb\"><img src=\"https://www.tensorflow.org/images/download_logo_32px.png\" />Download notebook</a>\n",
    "  </td>\n",
    "</table>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "ITZuApL56Mny"
   },
   "source": [
    "This tutorial demonstrates how to generate images of handwritten digits using a [Deep Convolutional Generative Adversarial Network](https://arxiv.org/pdf/1511.06434.pdf) (DCGAN). The code is written using the [Keras Sequential API](https://www.tensorflow.org/guide/keras) with a `tf.GradientTape` training loop."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "2MbKJY38Puy9"
   },
   "source": [
    "## What are GANs?\n",
    "[Generative Adversarial Networks](https://arxiv.org/abs/1406.2661) (GANs) are one of the most interesting ideas in computer science today. Two models are trained simultaneously by an adversarial process. A *generator* (\"the artist\") learns to create images that look real, while a *discriminator* (\"the art critic\") learns to tell real images apart from fakes.\n",
    "\n",
    "![A diagram of a generator and discriminator](./images/gan1.png)\n",
    "\n",
    "During training, the *generator* progressively becomes better at creating images that look real, while the *discriminator* becomes better at telling them apart. The process reaches equilibrium when the *discriminator* can no longer distinguish real images from fakes.\n",
    "\n",
    "![A second diagram of a generator and discriminator](./images/gan2.png)\n",
    "\n",
    "This notebook demonstrates this process on the MNIST dataset. The following animation shows a series of images produced by the *generator* as it was trained for 50 epochs. The images begin as random noise, and increasingly resemble hand written digits over time.\n",
    "\n",
    "![sample output](https://tensorflow.org/images/gan/dcgan.gif)\n",
    "\n",
    "To learn more about GANs, we recommend MIT's [Intro to Deep Learning](http://introtodeeplearning.com/) course."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "e1_Y75QXJS6h"
   },
   "source": [
    "### Import TensorFlow and other libraries"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "J5oue0oqCkZZ"
   },
   "outputs": [],
   "source": [
    "from __future__ import absolute_import, division, print_function, unicode_literals"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "g5RstiiB8V-z"
   },
   "outputs": [],
   "source": [
    "try:\n",
    "  # %tensorflow_version only exists in Colab.\n",
    "  %tensorflow_version 2.x\n",
    "except Exception:\n",
    "  pass\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "WZKbyU2-AiY-"
   },
   "outputs": [],
   "source": [
    "import tensorflow as tf"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "wx-zNbLqB4K8"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'2.0.0'"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "tf.__version__"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "YzTlj4YdCip_"
   },
   "outputs": [],
   "source": [
    "# To generate GIFs\n",
    "!pip install -q imageio"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "YfIk2es3hJEd"
   },
   "outputs": [],
   "source": [
    "import glob\n",
    "import imageio\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "import os\n",
    "import PIL\n",
    "from tensorflow.keras import layers\n",
    "import time\n",
    "\n",
    "from IPython import display"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "iYn4MdZnKCey"
   },
   "source": [
    "### Load and prepare the dataset\n",
    "\n",
    "You will use the MNIST dataset to train the generator and the discriminator. The generator will generate handwritten digits resembling the MNIST data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "a4fYMGxGhrna"
   },
   "outputs": [],
   "source": [
    "(train_images, train_labels), (_, _) = tf.keras.datasets.mnist.load_data()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "NFC2ghIdiZYE"
   },
   "outputs": [],
   "source": [
    "train_images = train_images.reshape(train_images.shape[0], 28, 28, 1).astype('float32')\n",
    "train_images = (train_images - 127.5) / 127.5 # Normalize the images to [-1, 1]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "S4PIDhoDLbsZ"
   },
   "outputs": [],
   "source": [
    "BUFFER_SIZE = 60000\n",
    "BATCH_SIZE = 256"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "-yKCCQOoJ7cn"
   },
   "outputs": [],
   "source": [
    "# Batch and shuffle the data\n",
    "train_dataset = tf.data.Dataset.from_tensor_slices(train_images).shuffle(BUFFER_SIZE).batch(BATCH_SIZE)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "THY-sZMiQ4UV"
   },
   "source": [
    "## Create the models\n",
    "\n",
    "Both the generator and discriminator are defined using the [Keras Sequential API](https://www.tensorflow.org/guide/keras#sequential_model)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "-tEyxE-GMC48"
   },
   "source": [
    "### The Generator\n",
    "\n",
    "The generator uses `tf.keras.layers.Conv2DTranspose` (upsampling) layers to produce an image from a seed (random noise). Start with a `Dense` layer that takes this seed as input, then upsample several times until you reach the desired image size of 28x28x1. Notice the `tf.keras.layers.LeakyReLU` activation for each layer, except the output layer which uses tanh."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "6bpTcDqoLWjY"
   },
   "outputs": [],
   "source": [
    "def make_generator_model():\n",
    "    model = tf.keras.Sequential()\n",
    "    model.add(layers.Dense(7*7*256, use_bias=False, input_shape=(100,)))\n",
    "    model.add(layers.BatchNormalization())\n",
    "    model.add(layers.LeakyReLU())\n",
    "\n",
    "    model.add(layers.Reshape((7, 7, 256)))\n",
    "    assert model.output_shape == (None, 7, 7, 256) # Note: None is the batch size\n",
    "\n",
    "    model.add(layers.Conv2DTranspose(128, (5, 5), strides=(1, 1), padding='same', use_bias=False))\n",
    "    assert model.output_shape == (None, 7, 7, 128)\n",
    "    model.add(layers.BatchNormalization())\n",
    "    model.add(layers.LeakyReLU())\n",
    "\n",
    "    model.add(layers.Conv2DTranspose(64, (5, 5), strides=(2, 2), padding='same', use_bias=False))\n",
    "    assert model.output_shape == (None, 14, 14, 64)\n",
    "    model.add(layers.BatchNormalization())\n",
    "    model.add(layers.LeakyReLU())\n",
    "\n",
    "    model.add(layers.Conv2DTranspose(1, (5, 5), strides=(2, 2), padding='same', use_bias=False, activation='tanh'))\n",
    "    assert model.output_shape == (None, 28, 28, 1)\n",
    "\n",
    "    return model"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "GyWgG09LCSJl"
   },
   "source": [
    "Use the (as yet untrained) generator to create an image."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "gl7jcC7TdPTG"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.image.AxesImage at 0x7f15828cd358>"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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RM6XyizpvRrAfwF418xWZ26bAOARAP9rZrcD2AJgeOwDuTudYoltWcy252ZbRAPAN77xDZrHpvo2b94czNiRyQAwbtw4mm/dupXmbAknAMycGf5/t6SkhNbeeOONNJ80aRLNY8eqr1u3LpjFjtg+ceIEzZ966imaf/nLXw5msa25V69eTfPYFtk333wzzdn3cmxq+KKLLgpm5/KrdbT53X0xAAvE11X6nkQkp+gKP5FEqflFEqXmF0mUml8kUWp+kUSp+UUSZeVX5taO1q1b+4gRI4J5bPvsvXv3BrN27drRWna8NxBfEtypU6dgFlseOnv2bJr36NGD5rEjwAcOHBjMXnrpJVp766230vztt9+m+YUXXkhzdnx4mzZtaG2XLl1oHruG4Q9/+EMwi133ERP7vF988UWa33nnncEsdrx3cXFxMJs4cSK2b98empr/DD3ziyRKzS+SKDW/SKLU/CKJUvOLJErNL5IoNb9Iomr1iO569erR+dE9e/bQerZdMtsiGgD69etH83fffZfms2bNCmbs6HAAKN8PJey2226j+TPPPEPzO+64I5gNGjSI1j744IM0//GPf0zziRMn0nzs2LHB7Nlnn6W1bD0+AFx88cU0b9SoUTCLHbF93XV8tfrTTz9N86uvvprmzJEjR6pcey70zC+SKDW/SKLU/CKJUvOLJErNL5IoNb9IotT8Iomq1Xn+vLw8ul86W/sNAC1atAhm77//Pq1lxzUD8eOe27ZtG8xixzVfdtllNL/nnntovn//fpr36dMnmDVt2pTWxvblf+yxx2h+/fXX05wdAR7bByF2vHhRURHNR40aVeXaJ598kuaxNfexvQbY/cf2b+jatWswY0d/n0nP/CKJUvOLJErNL5IoNb9IotT8IolS84skSs0vkqjoPL+ZdQDwZwCtATiASe7+ezN7CMAdAD5dhD/B3V9lH6u0tBQHDx6s8mDZGuzYXHtsr4Bjx47RnM3rbty4kdbG5rPHjx9P882bN9P89OnTweyBBx6gtezaCSB+7UVs/3p21kLs854+fTrNY2vuFy1aFMzuu+8+WjthwgSaX3DBBTSPzfNPnTo1mBUWFtLaNWvWBLNz6a/KXORTAuBed19pZk0ArDCzuZnsSXfnV4GISE6KNr+77wSwM/P2ETNbB4AfjyMiOe+cfuc3s44A+gL4d+amu8zsHTObYmbNAzVjzGy5mS0/evRotQYrIjWn0s1vZvkAZgC4x90PA5gIoAuAPij/yeDxs9W5+yR3L3T3wvz8/BoYsojUhEo1v5nVR3njT3X3VwDA3YvdvdTdywBMBsB3yBSRnBJtfivfevY5AOvc/YkKtxdUeLdbAayt+eGJyPkSPaLbzAYCWATgXQBlmZsnABiJ8h/5HUARgDszLw4GtW3b1tk207HpE7YM8vLLL6e1r7zyCs2vuuoqmrNllHXq8P9DX375ZZqz478BYPjw4TT/+9//HsxiU06xqbrYUul9+/bRnB19Hvua1a1bl+bLli2jOZu+jR3J3rhxY5r/61//ovmhQ4dozpaIt2/fntayae1zOaK7Mq/2LwZwtg9G5/RFJLfpCj+RRKn5RRKl5hdJlJpfJFFqfpFEqflFElWrW3cDfE68WbNmtJbNC586dYrW9u/fn+bFxcU0Z+OOXSNw7bXX0jy23fK6deto3qpVq2C2Y8cOWjtgwACaDxs2jObz58+nOZurj21/HduyPLak94UXXghmseO9Y/cdm8fv27cvzdn1FW3atKG1W7duDWaxbb8r0jO/SKLU/CKJUvOLJErNL5IoNb9IotT8IolS84skKrqev0bvzGwPgC0VbmoJILy3c3bl6thydVyAxlZVNTm2S9z9osq8Y602/+fu3Gy5u/NNyrMkV8eWq+MCNLaqytbY9GO/SKLU/CKJynbzT8ry/TO5OrZcHRegsVVVVsaW1d/5RSR7sv3MLyJZkpXmN7ObzGy9mX1kZvy41FpmZkVm9q6ZrTaz5VkeyxQz221mayvc1sLM5prZhszfZz0mLUtje8jMtmceu9VmdnOWxtbBzOab2ftm9p6Z3Z25PauPHRlXVh63Wv+x38zqAvgQwA0AtgF4G8BId+cbxNcSMysCUOjuWZ8TNrNBAI4C+LO798rc9iiA/e7+SOY/zubu/t85MraHABzN9snNmQNlCiqeLA3g2wBuQxYfOzKu4cjC45aNZ/5+AD5y903ufgrAXwEMzcI4cp67LwRw5q4SQwE8n3n7eZR/89S6wNhygrvvdPeVmbePAPj0ZOmsPnZkXFmRjeZvB6DiViTbkFtHfjuAOWa2wszGZHswZ9G6wslIuwC0zuZgziJ6cnNtOuNk6Zx57Kpy4nVN0wt+nzfQ3a8AMATATzI/3uYkL/+dLZemayp1cnNtOcvJ0v+Rzceuqide17RsNP92AB0q/Lt95rac4O7bM3/vBvA35N7pw8WfHpKa+Xt3lsfzH7l0cvPZTpZGDjx2uXTidTaa/20A3cysk5nlARgBYFYWxvE5ZtY480IMzKwxgBuRe6cPzwIwOvP2aAAzsziWz8iVk5tDJ0sjy49dzp147e61/gfAzSh/xX8jgPuzMYbAuDoDWJP58162xwbgJZT/GHga5a+N3A7gQgDzAGwA8AaAFjk0thdQfprzOyhvtIIsjW0gyn+kfwfA6syfm7P92JFxZeVx0xV+IonSC34iiVLziyRKzS+SKDW/SKLU/CKJUvOLJErNL5IoNb9Iov4PKLBt1WqjJOIAAAAASUVORK5CYII=\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "generator = make_generator_model()\n",
    "\n",
    "noise = tf.random.normal([1, 100])\n",
    "generated_image = generator(noise, training=False)\n",
    "\n",
    "plt.imshow(generated_image[0, :, :, 0], cmap='gray')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "D0IKnaCtg6WE"
   },
   "source": [
    "### The Discriminator\n",
    "\n",
    "The discriminator is a CNN-based image classifier."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "dw2tPLmk2pEP"
   },
   "outputs": [],
   "source": [
    "def make_discriminator_model():\n",
    "    model = tf.keras.Sequential()\n",
    "    model.add(layers.Conv2D(64, (5, 5), strides=(2, 2), padding='same',\n",
    "                                     input_shape=[28, 28, 1]))\n",
    "    model.add(layers.LeakyReLU())\n",
    "    model.add(layers.Dropout(0.3))\n",
    "\n",
    "    model.add(layers.Conv2D(128, (5, 5), strides=(2, 2), padding='same'))\n",
    "    model.add(layers.LeakyReLU())\n",
    "    model.add(layers.Dropout(0.3))\n",
    "\n",
    "    model.add(layers.Flatten())\n",
    "    model.add(layers.Dense(1))\n",
    "\n",
    "    return model"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "QhPneagzCaQv"
   },
   "source": [
    "Use the (as yet untrained) discriminator to classify the generated images as real or fake. The model will be trained to output positive values for real images, and negative values for fake images."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "gDkA05NE6QMs"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tf.Tensor([[0.00132017]], shape=(1, 1), dtype=float32)\n"
     ]
    }
   ],
   "source": [
    "discriminator = make_discriminator_model()\n",
    "decision = discriminator(generated_image)\n",
    "print (decision)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "0FMYgY_mPfTi"
   },
   "source": [
    "## Define the loss and optimizers\n",
    "\n",
    "Define loss functions and optimizers for both models.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "psQfmXxYKU3X"
   },
   "outputs": [],
   "source": [
    "# This method returns a helper function to compute cross entropy loss\n",
    "cross_entropy = tf.keras.losses.BinaryCrossentropy(from_logits=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "PKY_iPSPNWoj"
   },
   "source": [
    "### Discriminator loss\n",
    "\n",
    "This method quantifies how well the discriminator is able to distinguish real images from fakes. It compares the discriminator's predictions on real images to an array of 1s, and the discriminator's predictions on fake (generated) images to an array of 0s."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "wkMNfBWlT-PV"
   },
   "outputs": [],
   "source": [
    "def discriminator_loss(real_output, fake_output):\n",
    "    real_loss = cross_entropy(tf.ones_like(real_output), real_output)\n",
    "    fake_loss = cross_entropy(tf.zeros_like(fake_output), fake_output)\n",
    "    total_loss = real_loss + fake_loss\n",
    "    return total_loss"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "Jd-3GCUEiKtv"
   },
   "source": [
    "### Generator loss\n",
    "The generator's loss quantifies how well it was able to trick the discriminator. Intuitively, if the generator is performing well, the discriminator will classify the fake images as real (or 1). Here, we will compare the discriminators decisions on the generated images to an array of 1s."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "90BIcCKcDMxz"
   },
   "outputs": [],
   "source": [
    "def generator_loss(fake_output):\n",
    "    return cross_entropy(tf.ones_like(fake_output), fake_output)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "MgIc7i0th_Iu"
   },
   "source": [
    "The discriminator and the generator optimizers are different since we will train two networks separately."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "iWCn_PVdEJZ7"
   },
   "outputs": [],
   "source": [
    "generator_optimizer = tf.keras.optimizers.Adam(1e-4)\n",
    "discriminator_optimizer = tf.keras.optimizers.Adam(1e-4)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "mWtinsGDPJlV"
   },
   "source": [
    "### Save checkpoints\n",
    "This notebook also demonstrates how to save and restore models, which can be helpful in case a long running training task is interrupted."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "CA1w-7s2POEy"
   },
   "outputs": [],
   "source": [
    "checkpoint_dir = './training_checkpoints'\n",
    "checkpoint_prefix = os.path.join(checkpoint_dir, \"ckpt\")\n",
    "checkpoint = tf.train.Checkpoint(generator_optimizer=generator_optimizer,\n",
    "                                 discriminator_optimizer=discriminator_optimizer,\n",
    "                                 generator=generator,\n",
    "                                 discriminator=discriminator)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "Rw1fkAczTQYh"
   },
   "source": [
    "## Define the training loop\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "NS2GWywBbAWo"
   },
   "outputs": [],
   "source": [
    "EPOCHS = 50\n",
    "noise_dim = 100\n",
    "num_examples_to_generate = 16\n",
    "\n",
    "# We will reuse this seed overtime (so it's easier)\n",
    "# to visualize progress in the animated GIF)\n",
    "seed = tf.random.normal([num_examples_to_generate, noise_dim])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "jylSonrqSWfi"
   },
   "source": [
    "The training loop begins with generator receiving a random seed as input. That seed is used to produce an image. The discriminator is then used to classify real images (drawn from the training set) and fakes images (produced by the generator). The loss is calculated for each of these models, and the gradients are used to update the generator and discriminator."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "3t5ibNo05jCB"
   },
   "outputs": [],
   "source": [
    "# Notice the use of `tf.function`\n",
    "# This annotation causes the function to be \"compiled\".\n",
    "@tf.function\n",
    "def train_step(images):\n",
    "    noise = tf.random.normal([BATCH_SIZE, noise_dim])\n",
    "\n",
    "    with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape:\n",
    "      generated_images = generator(noise, training=True)\n",
    "\n",
    "      real_output = discriminator(images, training=True)\n",
    "      fake_output = discriminator(generated_images, training=True)\n",
    "\n",
    "      gen_loss = generator_loss(fake_output)\n",
    "      disc_loss = discriminator_loss(real_output, fake_output)\n",
    "\n",
    "    gradients_of_generator = gen_tape.gradient(gen_loss, generator.trainable_variables)\n",
    "    gradients_of_discriminator = disc_tape.gradient(disc_loss, discriminator.trainable_variables)\n",
    "\n",
    "    generator_optimizer.apply_gradients(zip(gradients_of_generator, generator.trainable_variables))\n",
    "    discriminator_optimizer.apply_gradients(zip(gradients_of_discriminator, discriminator.trainable_variables))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "2M7LmLtGEMQJ"
   },
   "outputs": [],
   "source": [
    "def train(dataset, epochs):\n",
    "  for epoch in range(epochs):\n",
    "    start = time.time()\n",
    "\n",
    "    for image_batch in dataset:\n",
    "      train_step(image_batch)\n",
    "\n",
    "    # Produce images for the GIF as we go\n",
    "    display.clear_output(wait=True)\n",
    "    generate_and_save_images(generator,\n",
    "                             epoch + 1,\n",
    "                             seed)\n",
    "\n",
    "    # Save the model every 15 epochs\n",
    "    if (epoch + 1) % 15 == 0:\n",
    "      checkpoint.save(file_prefix = checkpoint_prefix)\n",
    "\n",
    "    print ('Time for epoch {} is {} sec'.format(epoch + 1, time.time()-start))\n",
    "\n",
    "  # Generate after the final epoch\n",
    "  display.clear_output(wait=True)\n",
    "  generate_and_save_images(generator,\n",
    "                           epochs,\n",
    "                           seed)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "2aFF7Hk3XdeW"
   },
   "source": [
    "**Generate and save images**\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "RmdVsmvhPxyy"
   },
   "outputs": [],
   "source": [
    "def generate_and_save_images(model, epoch, test_input):\n",
    "  # Notice `training` is set to False.\n",
    "  # This is so all layers run in inference mode (batchnorm).\n",
    "  predictions = model(test_input, training=False)\n",
    "\n",
    "  fig = plt.figure(figsize=(4,4))\n",
    "\n",
    "  for i in range(predictions.shape[0]):\n",
    "      plt.subplot(4, 4, i+1)\n",
    "      plt.imshow(predictions[i, :, :, 0] * 127.5 + 127.5, cmap='gray')\n",
    "      plt.axis('off')\n",
    "\n",
    "  plt.savefig('image_at_epoch_{:04d}.png'.format(epoch))\n",
    "  plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "dZrd4CdjR-Fp"
   },
   "source": [
    "## Train the model\n",
    "Call the `train()` method defined above to train the generator and discriminator simultaneously. Note, training GANs can be tricky. It's important that the generator and discriminator do not overpower each other (e.g., that they train at a similar rate).\n",
    "\n",
    "At the beginning of the training, the generated images look like random noise. As training progresses, the generated digits will look increasingly real. After about 50 epochs, they resemble MNIST digits. This may take about one minute / epoch with the default settings on Colab."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "Ly3UN0SLLY2l"
   },
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAQIAAAD7CAYAAACBpZo1AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDMuMC4zLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvnQurowAAIABJREFUeJztnWlgVdW5hp9zkpAIhDAEEDAyqIhMyiSoKCCI4kBFitYiRRSpilYEhap1RK1StBQHWtRrwRFFkKooV5GiVpSCKIogo4gMCoQpSAiQ3B/7vmufhEimM8bv+UNITk722nudb73fsL4VKCgowDCMXzbBWF+AYRixxwyBYRhmCAzDMENgGAZmCAzDwAyBYRiYITAMAzMEhmEAydH8Y4FAIKGrlwoKCgKleZ2NMzEo7TgBgsFgwf//TuQuKEykpKQAEAwGOeqoowDYsWPHEcdqisAwjOgqAsNIVBJBCaSmpgI4FZCWlkZycuk+4mYIDCPBCQQ81X/SSScBcODAAQD279/Ptm3bSvUe5hoYhkEgmpInFsGlYNCzdXXq1AFg69at5X4vC6IV5pcyTojvsSo4KGWQl5d32GtKGqspAsMwKn+MoE+fPoX+/9Zbb8XoSozSEAgE3MomtZoIgbpYkZaWRm5uboXfxxSBYRiVVxEoNvDSSy8B0L9//1heToWpUqUKTzzxBABTpkwB4KOPPorlJYUVpbn69evHpZdeCsCbb74J+OP9pRMMBrn//vsBOP/88wFvXq9Zs6bC711pDUH16tUBP5Dy6aefxvJyyk2TJk0AWLp0KdWqVQOgRYsWAHTr1o38/PxYXVpYkBtw9NFHAzBs2DC6dOkCQMeOHQGYNm1aWORvPKLxn3jiiYDnuuqZyyVatWoVANdccw1ffvklAJ06dQJg7dq1YbkOcw0Mw6i8iuC6664DfEWQmZkJwO7du2N2TaVB17t06VIATjjhBMCThTt27AB8ddOzZ08yMjIAmD59erQvtcxo9UtKSiItLQ3wlcAVV1wBQKtWrahatSoADRs2BODKK6/k73//e7QvNywEAgEaNGgA+Kt+9+7dAbj++uvdvCwOFQa1a9cOgNzcXOfy6vfCFUg1RWAYRuVUBIFAgJ07d7qvwQ+uPP744zG7rqLIulepUgXwrPy0adMAPzYgBTNkyBDefffdQr/3+uuv07lzZwDef/99ALKzs6Nz8aWkevXqDBgwAMBda7169UhKSgLgmGOOAXAqICUlxcU99OxSU1NdMPHgwYPRu/gwMXjwYABuvvlmAKeGtDegKHPnzgWgV69eh/1M9+bss88G4Mknn+TQoUMVvsaEMQS6eZJXko3Lli3j+++/B3yZlJSUxE033QT4HxrVYccaTej69evTs2dPwP8Q6JrBe8AAo0ePBij0sEPHpKDowoULATj++OPLdV0pKSlhmVCh7wcwYsQIxowZA/ibYXT9oYQGPYtWyB199NHUqFEDiD9DVxLVq1fnt7/9LQC1atU67OcKgq5evRqA0047jZycnJ99v2OPPRaACy+8EPBcxxUrVlT4Os01MAwjvhWBVo727dvzt7/9DfAVwb59+wB45513uP766wE/uJKfn88333xT6PVPPfVU9C68GCSFtdpt3ryZ559/HvBVwrRp05yb8MMPP/zse+k9srOzXbBNrkFZ0T3Oz8931xgO0tPTAU/+7t+/H/CVj64/lNC/XbSicMWKFQlXXagx9unTx90LrfRSP/v37+ePf/wjAC+++CJwZNcnJSWFDz/8EID169cDuHleUUwRGIYRn4pAq1RWVhbgBVmUQlGARf7sQw895JSACPU3i/4sGoTWyhetmy/ueuQLF7drrDg09ttvv51//vOfADz66KPlutbQexWO4iSNV+OcNWsWzZs3B+Ccc84BICMjw72uOHUgpJQuvPBCF0BTEDjeFYLGdcopp7g4jlSPlMHs2bN5+eWXgeKVgOb6H/7wBwAeeOAB976KN1j60DCMsBF3iiApKYmaNWsC0LhxY8CLvMryyXI+88wzAMXWWSclJbmCC1nh22+/HcDVsUeDQCDg1E04I/Jizpw5XHTRRUB4fMVwrC56D616S5cu5cYbbwRwkf/MzExGjBgBeFFy8FfQnJwcF/dQluGMM87ghhtuAGDcuHEA7NmzB4iN4isNug9Lly51GRSt8MoUzJgxw82LUIWkUvKhQ4cCuPtXUFDA3XffDXDEzEJ5iDtDkJyc7CqxVFPfrFkzvvvuO8D/QM+cOfNn3+OEE07g1FNPde8H3mQCzzBE4kMZSmjOO5J7AapVq+bShR9//HHE/k550AchLy+PH3/8EcD9u3r1an7zm98U+3uBQIC6desCvrHv3r27y8E3a9YM8CtHt2/fHqER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\n",
      "text/plain": [
       "<Figure size 288x288 with 16 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 2min 9s, sys: 26.2 s, total: 2min 36s\n",
      "Wall time: 3min 35s\n"
     ]
    }
   ],
   "source": [
    "%%time\n",
    "train(train_dataset, EPOCHS)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "rfM4YcPVPkNO"
   },
   "source": [
    "Restore the latest checkpoint."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "XhXsd0srPo8c"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tensorflow.python.training.tracking.util.CheckpointLoadStatus at 0x7f16082cfdd8>"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "checkpoint.restore(tf.train.latest_checkpoint(checkpoint_dir))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "P4M_vIbUi7c0"
   },
   "source": [
    "## Create a GIF\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "WfO5wCdclHGL"
   },
   "outputs": [],
   "source": [
    "# Display a single image using the epoch number\n",
    "def display_image(epoch_no):\n",
    "  return PIL.Image.open('image_at_epoch_{:04d}.png'.format(epoch_no))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "5x3q9_Oe5q0A"
   },
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<PIL.PngImagePlugin.PngImageFile image mode=RGBA size=288x288 at 0x7F15828EC080>"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "display_image(EPOCHS)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "NywiH3nL8guF"
   },
   "source": [
    "Use `imageio` to create an animated gif using the images saved during training."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "IGKQgENQ8lEI"
   },
   "outputs": [],
   "source": [
    "anim_file = 'dcgan.gif'\n",
    "\n",
    "with imageio.get_writer(anim_file, mode='I') as writer:\n",
    "  filenames = glob.glob('image*.png')\n",
    "  filenames = sorted(filenames)\n",
    "  last = -1\n",
    "  for i,filename in enumerate(filenames):\n",
    "    frame = 2*(i**0.5)\n",
    "    if round(frame) > round(last):\n",
    "      last = frame\n",
    "    else:\n",
    "      continue\n",
    "    image = imageio.imread(filename)\n",
    "    writer.append_data(image)\n",
    "  image = imageio.imread(filename)\n",
    "  writer.append_data(image)\n",
    "\n",
    "import IPython\n",
    "if IPython.version_info > (6,2,0,''):\n",
    "  display.Image(filename=anim_file)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "cGhC3-fMWSwl"
   },
   "source": [
    "If you're working in Colab you can download the animation with the code below:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "uV0yiKpzNP1b"
   },
   "outputs": [],
   "source": [
    "try:\n",
    "  from google.colab import files\n",
    "except ImportError:\n",
    "   pass\n",
    "else:\n",
    "  files.download(anim_file)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "k6qC-SbjK0yW"
   },
   "source": [
    "## Next steps\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "xjjkT9KAK6H7"
   },
   "source": [
    "This tutorial has shown the complete code necessary to write and train a GAN. As a next step, you might like to experiment with a different dataset, for example the Large-scale Celeb Faces Attributes (CelebA) dataset [available on Kaggle](https://www.kaggle.com/jessicali9530/celeba-dataset/home). To learn more about GANs we recommend the [NIPS 2016 Tutorial: Generative Adversarial Networks](https://arxiv.org/abs/1701.00160).\n"
   ]
  }
 ],
 "metadata": {
  "accelerator": "GPU",
  "colab": {
   "collapsed_sections": [],
   "name": "dcgan.ipynb",
   "private_outputs": true,
   "provenance": [],
   "toc_visible": true,
   "version": "0.3.2"
  },
  "kernelspec": {
   "display_name": "Python 3",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
